
    /**
     * Default  width.
     * @const
     */
    var DEFAULT_WIDTH = 600;

    /**
     * Frames per second.
     * @const
     */
    var FPS = 60;

    /** @const */
    var IS_HIDPI = window.devicePixelRatio > 1;

    /** @const */
    var IS_MOBILE = window.navigator.userAgent.indexOf('Mobi') > -1;

    /** @const */
    var IS_TOUCH_ENABLED = 'ontouchstart' in window;

    function CanvasState(outerContainerId,opt_config){
        // Singleton
        if (CanvasState.instance_) {
            return CanvasState.instance_;
        }
        CanvasState.instance_ = this;
        
//        this.outerContainerEl = document.querySelector(outerContainerId);
        
        this.canvas = null;
        this.canvasCtx = null;
        
        this.time = 0;
        
        this.config = opt_config || CanvasState.config;
        
    };
    window['CanvasState'] = CanvasState;
    
    /**
     * CSS class names.
     * @enum {string}
     */
    CanvasState.classes = {
      CANVAS: 'canvas-a2bce3434',
      CONTAINER: 'sub-container',
    };
    
    /**
     * ID elememt.
     * @enum {string}
     */
    CanvasState.id = {
      CANVAS: 'canvas-a2bce3434',
      CONTAINER: 'sub-container',
    };
    
    /**
     * Default dimensions.
     * @enum {string}
     */
    CanvasState.defaultDimensions = {
            K_PIXEL_WIDTH : 256,
            K_PIXEL_HEIGHT : 256,
            K_PIECE_WIDTH : 10,
            K_PIECE_HEIGHT : 10,
    };
    
    /**
     * Key code mapping.
     * @enum {object}
     */
    CanvasState.keycodes = {
      JUMP: {'38': 1, '32': 1},  // Up, spacebar
      DUCK: {'40': 1},  // Down
      RESTART: {'13': 1}  // Enter
    };
    
    /**
     * Runner event names.
     * @enum {string}
     */
    CanvasState.events = {
      ANIM_END: 'webkitAnimationEnd',
      CLICK: 'click',
      KEYDOWN: 'keydown',
      KEYUP: 'keyup',
      MOUSEDOWN: 'mousedown',
      MOUSEUP: 'mouseup',
      RESIZE: 'resize',
      TOUCHEND: 'touchend',
      TOUCHSTART: 'touchstart',
      VISIBILITY: 'visibilitychange',
      BLUR: 'blur',
      FOCUS: 'focus',
      LOAD: 'load'
    };
    
    CanvasState.prototype = {
            
            /**
             * CanvasState initialiser.
             */
            init : function() {
              // Hide the static icon.
//              document.querySelector('.' + Runner.classes.ICON).style.visibility =
//                  'hidden';
              
              var canvas = document.createElement('canvas');
              canvas.id = CanvasState.id.CANVAS;
              canvas.width = CanvasState.defaultDimensions.K_PIXEL_WIDTH;
              canvas.height = CanvasState.defaultDimensions.K_PIXEL_HEIGHT;
              
              var container = document.getElementById(CanvasState.id.CONTAINER);
              container.appendChild(canvas);
              
              this.canvas = canvas;
              this.canvasCtx = canvas.getContext('2d');
              // this parent of canvas state
              this.startListening(this.canvas);
              this.update();
            },
            
            clearCanvasState : function() {
                this.canvasCtx.clearRect(0, 0, CanvasState.defaultDimensions.K_PIXEL_WIDTH,
                        CanvasState.defaultDimensions.K_PIXEL_HEIGHT);
            },
            
            showState : function(deltaTime){
                var gridline = new GridLinesActor(this.canvas);
                gridline.init();
                
                var imageSprite = new Image();
                imageSprite.src = "resources/img/canvas/gAPTU.png";
                this.sprite = new Sprite(this.canvas,{
                    width :410,
                    height : 47,
                    image : imageSprite,
                    totalFrames : 10,
                });
                
                this.sprite.Animate(deltaTime);
                this.sprite.draw();
            },
            
            /**
             * Update the canvas state frame.
             */
            update : function() {
//                console.log('on working render canvas state !!!');
                var now = performance.now();
                var deltaTime = now - (this.time || now);
                this.time = now;
               
                this.showState(deltaTime);
                this.requestAnimationFrameState();
            },
            
            /**
             * RequestAnimationFrame wrapper.
             */
            requestAnimationFrameState : function() {
                this.raqId = requestAnimationFrame(this.update.bind(this));
            },
            
            stop: function() {
                
                cancelAnimationFrame(this.raqId);
                this.raqId = 0;
            },
            /**
             * Bind relevant key / mouse / touch listeners.
             */
            startListening: function(ElementAddListener) {
                // Keys.
                ElementAddListener.addEventListener(CanvasState.events.KEYDOWN, this);
                ElementAddListener.addEventListener(CanvasState.events.KEYUP, this);
                
                if (IS_MOBILE) {
                  // Mobile only touch devices.
//                  this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
//                  this.touchController.addEventListener(Runner.events.TOUCHEND, this);
//                  this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
                } else {
                  // Mouse.
                    ElementAddListener.addEventListener(CanvasState.events.MOUSEDOWN, this);
                    ElementAddListener.addEventListener(CanvasState.events.MOUSEUP, this);
                }
             },
             
             /**
              * Remove all listeners.
              */
             stopListening: function(ElementAddListener) {
                 ElementAddListener.addEventListener(CanvasState.events.KEYDOWN, this);
                 ElementAddListener.addEventListener(CanvasState.events.KEYUP, this);

               if (IS_MOBILE) {
//                 this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
//                 this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
//                 this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
               } else {
//                  // Mouse.
                   ElementAddListener.addEventListener(CanvasState.events.MOUSEDOWN, this);
                   ElementAddListener.addEventListener(CanvasState.events.MOUSEUP, this);
               }
             },
             
             
             /**
              * Event handler.
              */
             handleEvent: function(e) {
                   return (function(evtType, events) {
                     switch (evtType) {
                       case events.KEYDOWN:
                           break;
                       case events.TOUCHSTART:
                           break;
                       case events.MOUSEDOWN:
                           this.onMouseDown(e);
                           break;
                       case events.KEYUP:
                           break;
                       case events.TOUCHEND:
                           break;
                       case events.MOUSEUP:
                           this.onMouseUp(e);
                           break;
                     }
                   }.bind(this))(e.type, CanvasState.events);
             },
             
             /**
              * Process mousedown.
              * @param {Event} e
              */
             onMouseDown : function(e){
                   console.log('on mouse down');
                   var kPieceWidth = CanvasState.defaultDimensions.K_PIECE_WIDTH;
                   var kPieceHeight = CanvasState.defaultDimensions.K_PIECE_HEIGHT;
                   var x;
                   var y;
                   if (e.pageX != undefined && e.pageY != undefined) {
                       x = e.pageX;
                       y = e.pageY;
                   } else {
                       x = e.clientX + document.body.scrollLeft +
                               document.documentElement.scrollLeft;
                       y = e.clientY + document.body.scrollTop +
                               document.documentElement.scrollTop;
                   }
                   
                   x -= this.canvas.offsetLeft;
                   y -= this.canvas.offsetTop;
                   
                   var cell = {
                           col : Math.floor(y/kPieceHeight),
                           row : Math.floor(x/kPieceWidth)
                   };
                   
                   var rect = this.canvas.getBoundingClientRect();
                   var mousePosition = {
                     x: e.clientX - rect.left,
                     y: e.clientY - rect.top
                   };
             },
             
             /**
              * Process mouseup.
              * @param {Event} e
              */
             onMouseUp : function(e){
                 console.log('on mouse up');
             },
             
             /**
              * Process keydown.
              * @param {Event} e
              */
             onKeyDown : function(){
                 console.log('on key down');
             },
             
             /**
              * Process keyup.
              * @param {Event} e
              */
             onKeyUp : function(){
                 console.log('on key up');
             },
             
    };
    
  //******************************************************************************
    
    /**
     * .
     * @param 
     */
    function GridLinesActor(canvasState){
        this.canvas = canvasState;
        this.canvasCtx = this.canvas.getContext('2d');
        
    }
    
  
    GridLinesActor.prototype = {
        /**
         * Initialise the GridLinesActor.
         */
        init: function() {
//          context.clearRect(0, 0, kPixelWidth, kPixelHeight);
            var kPixelWidth = CanvasState.defaultDimensions.K_PIXEL_WIDTH;
            var kPixelHeight = CanvasState.defaultDimensions.K_PIXEL_HEIGHT;
            var kPieceWidth = CanvasState.defaultDimensions.K_PIECE_WIDTH;
            var kPieceHeight = CanvasState.defaultDimensions.K_PIECE_HEIGHT;
            this.canvasCtx.beginPath();

            /* vertical lines */
            for (var x = 0; x <= kPixelWidth; x += kPieceWidth) {
                this.canvasCtx.moveTo(0.5 + x, 0);
                this.canvasCtx.lineTo(0.5 + x, kPixelHeight);
            }

            /* horizontal lines */
            for (var y = 0; y <= kPixelHeight; y += kPieceHeight) {
                this.canvasCtx.moveTo(0, 0.5 + y);
                this.canvasCtx.lineTo(kPixelWidth, 0.5 +  y);
            }

            /* draw it! */
            this.canvasCtx.strokeStyle = "#ccc";
            this.canvasCtx.stroke();
            this.canvasCtx.closePath();
        },
    };
  //******************************************************************************
  //******************************************************************************
    /**
     * See article by William Malone:
     * http://www.williammalone.com
     * .
     * @param 
     */
    function Sprite(canvasState,spriteConfig){
        this.canvas = canvasState;
        this.canvasCtx = this.canvas.getContext('2d');
        this.widthSprite = spriteConfig.width;
        this.heightSprite = spriteConfig.height;
        this.imageSprite = spriteConfig.image;
        this.totalFrames = spriteConfig.totalFrames;
        this.currentFrame = 0;
        this.timer = 0;
        this.interval = 5000;
    }
    
    Sprite.prototype = {
        init: function() {
            
        },
        
        /**
         * .
         * @param 
         */
        update : function(deltaTime){
            // Clear the canvas
            this.context.clearRect(0, 0, this.width, this.height);
        },
        
        /**
         * context.drawImage(
         *   @param img Source image object Sprite sheet
         *   @param sx  Source x    Frame index times frame width
         *   @param sy  Source y    0
         *   @param sw  Source width    Frame width
         *   @param sh  Source height   Frame height
         *   @param dx  Destination x   0
         *   @param dy  Destination y   0
         *   @param dw  Destination width   Frame width
         *   @param dh  Destination height  Frame height
         * )
         * @param deltaTime
         */
        draw : function(){
            // Draw the animation sprite
            this.canvasCtx.drawImage(
              this.imageSprite,
              this.currentFrame * this.widthSprite / this.totalFrames,
              0,
              this.widthSprite / this.totalFrames,
              this.heightSprite,
              0,
              0,
              this.widthSprite / this.totalFrames,
              this.heightSprite);
        },
        
        /**
         * .
         * @param deltaTime
         */
        AnimateRight : function(deltaTime){
            
        },
        
        /**
         * .
         * @param deltaTime
         */
        AnimateLeft : function(deltaTime){
            
        },
        
        /**
         * .
         * @param deltaTime
         */
        AnimateTop : function(deltaTime){
            
        },
        
        /**
         * .
         * @param deltaTime
         */
        AnimateButton : function(deltaTime){
            
        },
        
        /**
         * .
         * @param deltaTime
         */
        Animate : function(deltaTime){
            this.timer += deltaTime / 2;
            if (this.timer > this.interval)
            {
                this.currentFrame++;
                this.timer = 0;
                if (this.currentFrame > this.totalFrames)
                {
                    this.currentFrame = 0;
                }
            }
        },
        
        
    };
  //******************************************************************************
  